home *** CD-ROM | disk | FTP | other *** search
- /* $Id: datebook.c,v 1.47 2000/05/22 17:17:28 chrisf Exp $ */
-
- /*
- Hot Date - A DatebookDB displayer for the PalmPilot
- Copyright (C) 1999 Chris Faherty
-
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
- /*
- * Just so you know these routines are pulled from the Datebook source code
- * as provided by Palm. At least that is what I started from, I may have
- * touched a few lines to get it to compile with gcc without warnings.
- *
- * They are used to get a list of appointments for a particular day or a
- * range of days. It is fairly sophisticated to compute this stuff so I
- * didn't bother and just skarfed their code.
- */
-
- #include <Pilot.h>
- #include "callback.h"
- #include "hotdate.h"
- #include "datebook.h"
-
- extern struct sPrefsR *PrefsR;
- extern UInt apptAlarmLine;
-
- /*
- * Static function prototypes
- */
- static void ApptUnpack(ApptPackedDBRecordPtr src, ApptDBRecordPtr dest);
- static Int ApptListCompare(ApptInfoPtr a1, ApptInfoPtr a2, Long extra);
- static Boolean NextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP);
- static Boolean IsException(ApptDBRecordPtr apptRec, DateType date);
- static Boolean Datebk3Done(ApptPackedDBRecordPtr r);
-
- /***********************************************************************
- *
- * FUNCTION: DateCompare
- *
- * DESCRIPTION: This routine compares two dates.
- *
- * PARAMETERS: d1 - a date
- * d2 - a date
- *
- * RETURNED: if d1 > d2 returns a positive int
- * if d1 < d2 returns a negative int
- * if d1 = d2 returns zero
- *
- * NOTE: This routine treats the DateType structure like an unsigned int,
- * it depends on the fact the the members of the structure are ordered
- * year, month, day form high bit to low low bit.
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/12/95 Initial Revision
- *
- ***********************************************************************/
- Int DateCompare(DateType d1, DateType d2)
- {
- UInt int1, int2;
-
- int1 = DateToInt(d1);
- int2 = DateToInt(d2);
-
- if (int1 > int2) return (1);
- else if (int1 < int2) return (-1);
- return 0;
- }
-
- /***********************************************************************
- *
- * FUNCTION: TimeCompare
- *
- * DESCRIPTION: This routine compares two times. "No time" is represented
- * by minus one, and is considered less than all times.
- *
- * PARAMETERS: nothing
- *
- * RETURNED: if t1 > t2 returns a positive int
- * if t1 < t2 returns a negative int
- * if t1 = t2 returns zero
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/12/95 Initial Revision
- *
- ***********************************************************************/
- Int TimeCompare(TimeType t1, TimeType t2)
- {
- Int int1, int2;
-
- int1 = TimeToInt(t1);
- int2 = TimeToInt(t2);
-
- if (int1 > int2) return (1);
- else if (int1 < int2) return (-1);
- return 0;
- }
-
- /************************************************************
- *
- * FUNCTION: ApptUnpack
- *
- * DESCRIPTION: Fills in the ApptDBRecord structure
- *
- * PARAMETERS: database record
- *
- * RETURNS: the record unpacked
- *
- * CREATED: 1/25/95
- *
- * BY: Roger Flores
- *
- *************************************************************/
- static void ApptUnpack(ApptPackedDBRecordPtr src, ApptDBRecordPtr dest)
- {
- ApptDBRecordFlags flags;
- char *p;
-
- flags = src->flags;
- p = &src->firstField;
-
- dest->when = (ApptDateTimeType *) src;
-
- if (flags.alarm) {
- dest->alarm = (AlarmInfoType *) p;
- p += sizeof(AlarmInfoType);
- } else dest->alarm = NULL;
-
- if (flags.repeat) {
- dest->repeat = (RepeatInfoType *) p;
- p += sizeof(RepeatInfoType);
- } else dest->repeat = NULL;
-
- if (flags.exceptions) {
- dest->exceptions = (ExceptionsListType *) p;
- p += sizeof(UInt) +
- (((ExceptionsListType *) p)->numExceptions * sizeof(DateType));
- } else dest->exceptions = NULL;
-
- if (flags.description) {
- dest->description = p;
- p += StrLen(p) + 1;
- } else dest->description = NULL;
-
- if (flags.note) {
- dest->note = p;
- } else dest->note = NULL;
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptRepeatsOnDate
- *
- * DESCRIPTION: This routine returns true if a repeating appointment
- * occurrs on the specified date.
- *
- * PARAMETERS: apptRec - a pointer to an appointment record
- * date - date to check
- *
- * RETURNED: true if the appointment occurs on the date specified
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/14/95 Initial Revision
- *
- ***********************************************************************/
- Boolean ApptRepeatsOnDate(ApptDBRecordPtr apptRec, DateType date)
- {
- Int i;
- Word freq;
- Word weeksDiff;
- Word dayInMonth;
- Word dayOfWeek;
- Word dayOfMonth;
- Word firstDayOfWeek;
- long dateInDays;
- long startInDays;
- Boolean onDate;
- DatePtr exceptions;
- DateType startDate;
-
- /* Is the date passed before the start date of the appointment? */
- if (DateCompare (date, apptRec->when->date) < 0) return (false);
-
- /* Is the date passed after the end date of the appointment? */
- if (DateCompare (date, apptRec->repeat->repeatEndDate) > 0) return (false);
-
- /*
- * Get the frequency of occurrecne (ex: every 2nd day,
- * every 3rd month, etc.).
- */
- freq = apptRec->repeat->repeatFrequency;
-
- /* Get the date of the first occurrecne of the appointment. */
- startDate = apptRec->when->date;
-
- switch (apptRec->repeat->repeatType) {
- /* Daily repeating appointment. */
- case repeatDaily:
- dateInDays = DateToDays(date);
- startInDays = DateToDays(startDate);
- onDate = ((dateInDays - startInDays) % freq) == 0;
- break;
-
- /*
- * Weekly repeating appointment (ex: every Monday and Friday).
- * Yes, weekly repeating appointment can occur more then once a
- * week.
- */
- case repeatWeekly:
- /* Are we on a day of the week that the appointment repeats on. */
- dayOfWeek = DayOfWeek(date.month, date.day, date.year+firstYear);
- onDate = ((1 << dayOfWeek) & apptRec->repeat->repeatOn);
- if (! onDate) break;
-
- /*
- * Are we in a week in which the appointment occurrs, if not
- * move to that start of the next week in which the appointment
- * does occur.
- */
- dateInDays = DateToDays(date);
- startInDays = DateToDays(startDate);
-
- firstDayOfWeek = (DayOfWeek(1, 1, firstYear) -
- apptRec->repeat->repeatStartOfWeek + daysInWeek) % daysInWeek;
-
- weeksDiff = (((dateInDays + firstDayOfWeek) / daysInWeek) -
- ((startInDays + firstDayOfWeek) / daysInWeek)) %freq;
- onDate = (weeksDiff == 0);
- break;
-
- /*
- * Monthly-by-day repeating appointment (ex: the 3rd Friday of every
- * month).
- */
- case repeatMonthlyByDay:
- /* Are we in a month in which the appointment repeats. */
- onDate = ((((date.year - startDate.year) * monthsInYear) +
- (date.month - startDate.month)) % freq) == 0;
- if (! onDate) break;
-
- /* Do the days of the month match (ex: 3rd Friday) */
- dayOfMonth = DayOfMonth(date.month, date.day, date.year+firstYear);
- onDate = (dayOfMonth == apptRec->repeat->repeatOn);
- if (onDate) break;
-
- /*
- * If the appointment repeats on one of the last days of the month,
- * check if the date passed is also one of the last days of the
- * month. By last days of the month we mean: last sunday,
- * last monday, etc.
- */
- if ((apptRec->repeat->repeatOn >= domLastSun) &&
- (dayOfMonth >= dom4thSun)) {
- dayOfWeek = DayOfWeek(date.month, date.day, date.year+firstYear);
- dayInMonth = DaysInMonth(date.month, date.year+firstYear);
- onDate = (((date.day + daysInWeek) > dayInMonth) &&
- (dayOfWeek == (apptRec->repeat->repeatOn % daysInWeek)));
- }
- break;
-
- /*
- * Monthly-by-date repeating appointment (ex: the 15th of every
- * month).
- */
- case repeatMonthlyByDate:
- /* Are we in a month in which the appointment repeats. */
- onDate = ((((date.year - startDate.year) * monthsInYear) +
- (date.month - startDate.month)) % freq) == 0;
- if (! onDate) break;
-
- /* Are we on the same day of the month as the start date. */
- onDate = (date.day == startDate.day);
- if (onDate) break;
-
- /*
- * If the staring day of the appointment is greater then the
- * number of day in the month passed, and the day passed is the
- * last day of the month, then the appointment repeats on the day.
- */
- dayInMonth = DaysInMonth(date.month, date.year+firstYear);
- onDate = ((startDate.day > dayInMonth) && (date.day == dayInMonth));
- break;
-
- /* Yearly repeating appointment. */
- case repeatYearly:
- /* Are we in a year in which the appointment repeats. */
- onDate = ((date.year - startDate.year) % freq) == 0;
- if (! onDate) break;
-
- /* Are we on the month and day that the appointment repeats. */
- onDate = (date.month == startDate.month) &&
- (date.day == startDate.day);
- if (onDate) break;
-
- /* Specal leap day processing. */
- if ((startDate.month == february) &&
- (startDate.day == 29) &&
- (date.month == february) &&
- (date.day == DaysInMonth(date.month, date.year+firstYear))) {
- onDate = true;
- }
- break;
-
- /* this is to get rid of the compiler warning */
- default:
- onDate = false;
- break;
- }
-
- /* Check for an exception. */
- if ((onDate) && (apptRec->exceptions)) {
- exceptions = &apptRec->exceptions->exception;
- for (i=0; i < apptRec->exceptions->numExceptions; i++) {
- if (DateCompare(date, exceptions[i]) == 0) {
- onDate = false;
- break;
- }
- }
- }
-
- return (onDate);
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptFindFirst
- *
- * DESCRIPTION: This routine finds the first appointment on the specified
- * day.
- *
- * PARAMETERS: dbP - pointer to the database
- * date - date to search for
- * indexP - pointer to the index of the first record on the
- * specified day (returned value)
- *
- * RETURNED: true if a record has found
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/15/95 Initial Revision
- *
- ***********************************************************************/
- Boolean ApptFindFirst(DmOpenRef dbP, DateType date, UIntPtr indexP)
- {
- Err err;
- Int numOfRecords;
- Int kmin, probe, i; /* all positions in the database. */
- Int result = 0; /* result of comparing two records */
- UInt index;
- VoidHand recordH;
- Boolean found = false;
- ApptPackedDBRecordPtr r;
-
- kmin = probe = 0;
- numOfRecords = DmNumRecords(dbP);
-
- while (numOfRecords > 0) {
- i = numOfRecords >> 1;
- probe = kmin + i;
-
- index = probe;
- recordH = DmQueryNextInCategory(dbP, &index, dmAllCategories);
- if (recordH) {
- r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
- if (r->flags.repeat) result = 1;
- else result = DateCompare(date, r->when.date);
- MemHandleUnlock(recordH);
- }
- /*
- * If no handle, assume the record is deleted, deleted records
- * are greater.
- */
- else result = -1;
-
- /* If the date passed is less than the probe's date, keep searching. */
- if (result < 0) numOfRecords = i;
-
- /*
- * If the date passed is greater than the probe's date, keep searching.
- */
- else if (result > 0) {
- kmin = probe + 1;
- numOfRecords = numOfRecords - i - 1;
- }
-
- /* If the records are equal find the first record on the day. */
- else {
- found = true;
- *indexP = index;
- while (true) {
- err = DmSeekRecordInCategory(dbP, &index, 1, dmSeekBackward,
- dmAllCategories);
- if (err == dmErrSeekFailed) break;
-
- recordH = DmQueryRecord(dbP, index);
- r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
- if (r->flags.repeat) result = 1;
- else result = DateCompare(date, r->when.date);
- MemHandleUnlock(recordH);
- if (result != 0) break;
- *indexP = index;
- }
-
- break;
- }
- }
-
- /*
- * If that were no appointments on the specified day, return the
- * index of the next appointment (on a future day).
- */
- if (! found) {
- if (result < 0) *indexP = probe;
- else *indexP = probe + 1;
- }
-
- return (found);
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptListCompare
- *
- * DESCRIPTION: This routine compares two entries in the appointment list,
- * it's called by ApptGetAppointments via the quick sort
- * routine.
- *
- * PARAMETERS: a - a pointer to an entry in the appointment list
- * b - a pointer to an entry in the appointment list
- * extra - extra data passed to quick sort - not used
- *
- * RETURNED: if a1 > a2 returns a positive int
- * if a1 < a2 returns a negative int
- * if a1 = a2 returns zero
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/15/95 Initial Revision
- *
- ***********************************************************************/
- static Int ApptListCompare(ApptInfoPtr a1, ApptInfoPtr a2, Long extra)
- {
- Int result;
-
- CALLBACK_PROLOGUE
-
- result = DateCompare(a1->date, a2->date);
- if (result == 0) {
- result = TimeCompare(a1->startTime, a2->startTime);
- if (result == 0) {
- result = TimeCompare(a1->endTime, a2->endTime);
- }
- }
-
- CALLBACK_EPILOGUE
-
- return result;
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptNextRepeat
- *
- * DESCRIPTION: This routine computes the date of the next
- * occurrence of a repeating appointment.
- *
- * PARAMETERS: apptRec - a pointer to an appointment record
- * date - passed: date to start from
- * returned: date of next occurrence
- *
- * RETURNED: true if the appointment occurs again
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/14/95 Initial Revision
- *
- ***********************************************************************/
- static Boolean NextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP)
- {
- Int i;
- Word day;
- Word freq;
- Word year;
- Word adjust;
- Word weeksDiff;
- Word monthsDiff;
- Word daysInMonth;
- Word dayOfWeek;
- Word apptWeekDay;
- Word firstDayOfWeek;
- Word daysTilNext;
- Word monthsTilNext;
- ULong dateInDays;
- ULong startInDays;
- DateType start;
- DateType date;
- DateType next;
-
- date = *dateP;
-
- /* Is the date passed after the end date of the appointment? */
- if (DateCompare(date, apptRec->repeat->repeatEndDate) > 0)
- return (false);
-
- /* Is the date passed before the start date of the appointment? */
- if (DateCompare(date, apptRec->when->date) < 0)
- date = apptRec->when->date;
-
- /*
- * Get the frequency on occurrecne (ex: every 2nd day,
- * every 3rd month, etc).
- */
- freq = apptRec->repeat->repeatFrequency;
-
- /* Get the date of the first occurrecne of the appointment. */
- start = apptRec->when->date;
-
- switch (apptRec->repeat->repeatType) {
- /* Daily repeating appointment. */
- case repeatDaily:
- dateInDays = DateToDays(date);
- startInDays = DateToDays(start);
- daysTilNext = (dateInDays - startInDays + freq - 1) / freq * freq;
- if (startInDays + daysTilNext > (ULong) maxDays) return (false);
- DateDaysToDate(startInDays + daysTilNext, &next);
- break;
-
- /*
- * Weekly repeating appointment (ex: every Monday and Friday).
- * Yes, weekly repeating appointment can occur more then once a
- * week.
- */
- case repeatWeekly:
- dateInDays = DateToDays(date);
- startInDays = DateToDays(start);
-
- firstDayOfWeek = (DayOfWeek (1, 1, firstYear) -
- apptRec->repeat->repeatStartOfWeek + daysInWeek) % daysInWeek;
-
- dayOfWeek = DayOfWeek (date.month, date.day, date.year+firstYear);
- apptWeekDay = (dayOfWeek - apptRec->repeat->repeatStartOfWeek +
- daysInWeek) % daysInWeek;
-
- /*
- * Are we in a week in which the appointment occurrs, if not
- * move to that start of the next week in which the appointment
- * does occur.
- */
- weeksDiff = (((dateInDays + firstDayOfWeek) / daysInWeek) -
- ((startInDays + firstDayOfWeek) / daysInWeek)) %freq;
- if (weeksDiff) {
- adjust = ((freq - weeksDiff) * daysInWeek)- apptWeekDay;
- apptWeekDay = 0;
- dayOfWeek = (dayOfWeek + adjust) % daysInWeek;
- } else adjust = 0;
-
- /* Find the next day on which the appointment repeats. */
- for (i=0; i < daysInWeek; i++) {
- if (apptRec->repeat->repeatOn & (1 << dayOfWeek)) break;
- adjust++;
- if (++dayOfWeek == daysInWeek) dayOfWeek = 0;
- if (++apptWeekDay == daysInWeek) adjust += (freq - 1) * daysInWeek;
- }
-
- if (dateInDays + adjust > (ULong) maxDays) return (false);
- DateDaysToDate(dateInDays + adjust, &next);
- break;
-
- /*
- * Monthly-by-day repeating appointment (ex: the 3rd Friday of every
- * month).
- */
- case repeatMonthlyByDay:
- /* Compute the number of month until the appointment repeats again. */
- monthsTilNext = (date.month - start.month);
- monthsTilNext = ((((date.year - start.year) * monthsInYear) +
- (date.month - start.month)) + freq - 1) / freq * freq;
-
- while (true) {
- year = start.year +
- (start.month - 1 + monthsTilNext) / monthsInYear;
- if (year >= numberOfYears) return (false);
-
- next.year = year;
- next.month = (start.month - 1 + monthsTilNext) % monthsInYear + 1;
-
- dayOfWeek = DayOfWeek(next.month, 1, next.year+firstYear);
- if ((apptRec->repeat->repeatOn % daysInWeek) >= dayOfWeek)
- day = apptRec->repeat->repeatOn - dayOfWeek + 1;
- else
- day = apptRec->repeat->repeatOn + daysInWeek - dayOfWeek + 1;
-
- /*
- * If repeat-on day is between the last sunday and the last
- * saturday, make sure we're not passed the end of the month.
- */
- if ((apptRec->repeat->repeatOn >= domLastSun) &&
- (day > DaysInMonth (next.month, next.year+firstYear))) {
- day -= daysInWeek;
- }
- next.day = day;
-
- /*
- * Its posible that "next date" calculated above is
- * before the date passed. If so, move forward
- * by the length of the repeat freguency and preform
- * the calculation again.
- */
- if (DateToInt(date) > DateToInt (next)) monthsTilNext += freq;
- else break;
- }
- break;
-
- /*
- * Monthly-by-date repeating appointment (ex: the 15th of every
- * month).
- */
- case repeatMonthlyByDate:
- /* Compute the number of month until the appointment repeats again. */
- monthsDiff = ((date.year - start.year) * monthsInYear) +
- (date.month - start.month);
- monthsTilNext = (monthsDiff + freq - 1) / freq * freq;
-
- if ((date.day > start.day) && (!(monthsDiff % freq)))
- monthsTilNext += freq;
-
- year = start.year +
- (start.month - 1 + monthsTilNext) / monthsInYear;
- if (year >= numberOfYears) return (false);
-
- next.year = year;
- next.month = (start.month - 1 + monthsTilNext) % monthsInYear + 1;
- next.day = start.day;
-
- /* Make sure we're not passed the last day of the month. */
- daysInMonth = DaysInMonth(next.month, next.year+firstYear);
- if (next.day > daysInMonth) next.day = daysInMonth;
- break;
-
- /* Yearly repeating appointment. */
- case repeatYearly:
- next.day = start.day;
- next.month = start.month;
-
- year = start.year +
- ((date.year - start.year + freq - 1) / freq * freq);
-
- if ((date.month > start.month) ||
- ((date.month == start.month) && (date.day > start.day)))
- year += freq;
-
- /* Specal leap day processing. */
- if ((next.month == february) && (next.day == 29) &&
- (next.day > DaysInMonth (next.month, year+firstYear))) {
- next.day = DaysInMonth (next.month, year+firstYear);
- }
- if (year >= numberOfYears) return (false);
-
- next.year = year;
- break;
-
- /* this is to get rid of gcc warning */
- default:
- return (false);
- }
-
- /* Is the next occurrence after the end date of the appointment? */
- if (DateCompare(next, apptRec->repeat->repeatEndDate) > 0)
- return (false);
-
- ErrFatalDisplayIf((DateToInt(next) < DateToInt(*dateP)),
- "Calculation error");
-
- *dateP = next;
- return (true);
- }
-
- /***********************************************************************
- *
- * FUNCTION: IsException
- *
- * DESCRIPTION: This routine returns true the date passed is in a
- * repeating appointment's exception list.
- *
- * PARAMETERS: apptRec - a pointer to an appointment record
- * date - date to check
- *
- * RETURNED: true if the date is an exception date.
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/14/95 Initial Revision
- *
- ***********************************************************************/
- static Boolean IsException(ApptDBRecordPtr apptRec, DateType date)
- {
- int i;
- DatePtr exceptions;
-
- if (apptRec->exceptions) {
- exceptions = &apptRec->exceptions->exception;
- for (i=0; i < apptRec->exceptions->numExceptions; i++) {
- if (DateCompare(date, exceptions[i]) == 0) return (true);
- }
- }
- return (false);
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptNextRepeat
- *
- * DESCRIPTION: This routine computes the next occurrence of a
- * repeating appointment.
- *
- * PARAMETERS: apptRec - a pointer to an appointment record
- * dateP - passed: date to start from
- * returned: date of next occurrence
- *
- * RETURNED: true if there is an occurrence of the appointment
- * between the date passed and the appointment's end date
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/20/95 Initial Revision
- *
- ***********************************************************************/
- Boolean ApptNextRepeat(ApptDBRecordPtr apptRec, DatePtr dateP)
- {
- DateType date;
-
- date = *dateP;
-
- while (true) {
- /* Compute the next time the appointment repeats. */
- if (! NextRepeat(apptRec, &date)) return (false);
-
- /* Check if the date computed is in the exceptions list. */
- if (! IsException(apptRec, date)) {
- *dateP = date;
- return (true);
- }
-
- DateAdjust(&date, 1);
- }
- }
-
- /***********************************************************************
- *
- * FUNCTION: AddAppointmentToList
- *
- * DESCRIPTION: This routine adds an appointment to a list of appointments.
- *
- * PARAMETERS: dbP - pointer to the database
- * date - date to search for
- * countP - number of appointments on the specified
- * day (returned value)
- * type - 0 for datebook 1 for todo
- *
- * RETURNED: handle of the appointment list (ApptInfoType)
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 4/17/96 Initial Revision
- *
- ***********************************************************************/
- Boolean AddAppointmentToList(VoidHand * apptListH, ULong shrunk, UInt count,
- UInt origcount, TimeType startTime, TimeType endTime, DateType date,
- UInt recordNum, Word type)
- {
- Err err;
- Word newSize;
- ApptInfoPtr apptList;
-
- if ((count+origcount) >= apptMaxPerDay) return false;
-
- if ((count == 0) && ! *apptListH) {
- /* Allocated a block to hold the appointment list. */
- *apptListH = MemHandleNew(sizeof(ApptInfoType) * (apptMaxPerDay / 10));
- /*
- * This code comes from DateDB.c (Datebook source). The next two
- * lines are incorrect in that source, but corrected here. Thanks to
- * Jane B. Halfond.
- */
- ErrFatalDisplayIf(!(*apptListH), "Out of memory");
- if (!(*apptListH)) return (false);
- apptList = MemHandleLock(*apptListH);
- }
-
- /*
- * Resize the list to hold more more appointments. Keep in mind that in
- * some cases we are appending ApptInfoType structures to memory that
- * has LineItemType structures. The shrunk parameter is used to skip
- * those LineItemType structures at the beginning. In other words, the
- * ApptInfoType is used during collection and sorting.. and then they
- * are shrunk into a LineItemType to save memory.
- */
- else if (((count % (apptMaxPerDay / 10)) == 0) ||
- ((MemHandleSize(*apptListH)-shrunk) <
- (sizeof(ApptInfoType)*(((count+(apptMaxPerDay/10))/(apptMaxPerDay/10))
- *(apptMaxPerDay/10))))) {
- if (count + (apptMaxPerDay / 10) > apptMaxPerDay) return (false);
-
- MemHandleUnlock(*apptListH);
- newSize = shrunk+(sizeof(ApptInfoType)*
- ((count+(apptMaxPerDay/10))/
- (apptMaxPerDay/10))*(apptMaxPerDay/10));
- err = MemHandleResize(*apptListH, newSize);
- apptList = MemHandleLock(*apptListH);
- /*
- * Hopefully this will just gracefully abort the operation.
- */
- if (err) return (false);
-
- } else {
- MemHandleUnlock(*apptListH);
- apptList = MemHandleLock(*apptListH);
- }
-
- /*
- * Skip the data that is already shrunk at the beginning.
- */
- apptList = (ApptInfoPtr) (((char *) apptList)+shrunk);
-
- apptList[count].startTime = startTime;
- apptList[count].endTime = endTime;
- apptList[count].recordNum = recordNum;
- apptList[count].date = date;
- apptList[count].type = type;
-
- return (true);
- }
-
- /*
- * Datebk3 has a "done" checkbox that you can pick for an appointment. This
- * function tests to see if it is set.
- *
- * http://www.gorilla-haven.org/pimlico/datebk3doc.htm
- */
- static Boolean Datebk3Done(ApptPackedDBRecordPtr r)
- {
- long l1;
-
- /* Calculate the position of the description */
- l1 = sizeof(ApptDateTimeType)+sizeof(ApptDBRecordFlags);
- if (r->flags.alarm) l1 += sizeof(AlarmInfoType);
- if (r->flags.repeat) l1 += sizeof(RepeatInfoType);
- if (r->flags.exceptions)
- l1 += sizeof(UInt) +
- (((ExceptionsListType *)
- ((CharPtr) r+l1))->numExceptions*sizeof(DateType));
- if (r->flags.description) l1 += StrLen((CharPtr) r+l1)+1;
-
- /* Check the note and see if it has the done flag */
- if (r->flags.note) {
- if ((StrLen((CharPtr) r+l1) >= 10) &&
- (*((CharPtr) r+l1) == '#') && (*((CharPtr) r+l1+1) == '#') &&
- (*((CharPtr) r+l1+2) == 'c') && (*((CharPtr) r+l1+9) == '\n')) {
- return true;
- }
- }
- return false;
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptGetAppointments
- *
- * DESCRIPTION: This routine returns a list of appointments that are in
- * the range of dates specified
- *
- * PARAMETERS: dbP - pointer to the database
- * date - start date to search from
- * days - number a days in search range
- * apptLists - returned: array of handle of the
- * appointment list (ApptInfoType)
- * counts - returned: returned: array of counts of the
- * number of appointments in each list.
- *
- * RETURNED: total number of appointments found
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 4/7/96 Initial Revision
- *
- ***********************************************************************/
- UInt ApptGetAppointments(DmOpenRef dbP, DateType date, Word days,
- VoidHand apptLists [], UInt counts [])
- {
- ApptInfoPtr apptList;
- LineItemPtr LineItem;
- LineItemType tli;
- UInt startDate;
- UInt endDate;
- UInt index, i;
- UInt recordNum;
- UInt totalcount=0;
- ULong dateInDays;
- DateType eventDate;
- TimeType startTime;
- TimeType endTime;
- DateType tempDate;
- DateType repeatDate;
- Boolean repeats, dbk3done;
- VoidHand recordH;
- ApptDBRecordType apptRec;
- ApptPackedDBRecordPtr r;
-
- MemSet(apptLists, days * sizeof(VoidHand), 0);
- MemSet(counts, days * sizeof(UInt), 0);
-
- startDate = DateToInt(date);
- tempDate = date;
- DateAdjust(&tempDate, days-1);
- endDate = DateToInt(tempDate);
-
- /* Find the first non-repeating appointment of the day. */
- ApptFindFirst(dbP, date, &recordNum);
- while (true) {
- recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
- if (! recordH) break;
-
- /*
- * Check if the appointment is on the date passed, if it is
- * add it to the appointment list.
- */
- r = MemHandleLock(recordH);
- startTime = r->when.startTime;
- endTime = r->when.endTime;
- eventDate = r->when.date;
- /* Check if the datebk3 "done" flag is set */
- dbk3done = (PrefsR->ApptPrefs.showdone == '0') && Datebk3Done(r);
- MemHandleUnlock(recordH);
-
- if ((DateToInt(eventDate) < startDate) ||
- (DateToInt(eventDate) > endDate)) break;
-
- if (!dbk3done) {
- /* Add the record to the appointment list. */
- index = DateToDays(eventDate) - DateToDays(date);
-
- if (AddAppointmentToList(&apptLists[index], 0, counts[index], 0,
- startTime, endTime, eventDate, recordNum, 0)) {
- counts[index]++;
- totalcount++;
- } else break;
- }
-
- recordNum++;
- }
-
- /*
- * Add the repeating appointments to the list. Repeating appointments
- * are stored at the beginning of the database.
- */
- recordNum = 0;
- dateInDays = DateToDays(date);
- while (true) {
- recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
- if (! recordH) break;
-
- r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
- repeats = (r->flags.repeat != 0);
-
- if (repeats) {
- ApptUnpack(r, &apptRec);
-
- if (days == 1) {
-
- if (ApptRepeatsOnDate(&apptRec, date)) {
- /* Check if the datebk3 "done" flag is set */
- dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
- Datebk3Done(r);
- if (!dbk3done && AddAppointmentToList(apptLists, 0,
- *counts, 0, r->when.startTime, r->when.endTime,
- date, recordNum, 0)) {
- (*counts)++;
- totalcount++;
- }
- }
- } else {
- repeatDate = date;
- while (ApptNextRepeat (&apptRec, &repeatDate)) {
- if (DateToInt(repeatDate) > endDate) break;
- /* Add the record to the appointment list. */
- index = DateToDays(repeatDate) - dateInDays;
- /* Check if the datebk3 "done" flag is set */
- dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
- Datebk3Done(r);
- if (!dbk3done && AddAppointmentToList(&apptLists[index], 0,
- counts[index], 0, r->when.startTime, r->when.endTime,
- repeatDate, recordNum, 0)) {
- counts[index]++;
- totalcount++;
- } else break;
-
- if (DateToInt(repeatDate) == endDate) break;
-
- DateAdjust(&repeatDate, 1);
- }
- }
- }
-
- MemHandleUnlock(recordH);
-
- /*
- * If the record has no repeating info we've reached the end of the
- * repeating appointments.
- */
- if (! repeats) break;
-
- recordNum++;
- }
-
- /* Sort the list by start time. */
- for (index=0; index < days; index ++) {
- if (apptLists[index]) {
- /*
- * Get the pointer again. Yeah I know this sucks having to
- * deref the handle again but I didn't want to have to carry
- * around a pointer.
- */
- MemHandleUnlock(apptLists[index]);
- apptList = MemHandleLock(apptLists[index]);
- if (counts[index] >= 2) {
- SysInsertionSort(apptList, counts[index], sizeof(ApptInfoType),
- (_comparF *) ApptListCompare, 0L);
- }
- /*
- * Shrink the array down to a more compact structure because we
- * don't need many of the structure elements anymore.
- */
- LineItem = (LineItemPtr) apptList;
- for (i=0; i < counts[index]; i++) {
- /*
- * Both LineItem & apptList point to the same spot so make
- * sure to use a temporary holding place when copying.
- */
- tli.recordNum = apptList[i].recordNum;
- tli.startTime = apptList[i].startTime;
- tli.date = apptList[i].date;
- LineItem[i] = tli;
- }
- MemHandleUnlock(apptLists[index]);
- MemHandleResize(apptLists[index], counts[index] *
- sizeof(LineItemType));
- }
- }
- return totalcount;
- }
-
- /***********************************************************************
- *
- * FUNCTION: ApptGetAlarmTime
- *
- * DESCRIPTION: This routine determines the date and time of the next alarm
- * for the appointment passed.
- *
- * PARAMETERS: apptRec - pointer to an appointment record
- * currentTime - current date and time in seconds
- *
- * RETURNED: date and time of the alarm, in seconds, or zero if there
- * is no alarm
- *
- * REVISION HISTORY:
- * Name Date Description
- * ---- ---- -----------
- * art 6/20/95 Initial Revision
- *
- * 8/9/99 - I am changing this to always return the alarm time even if it
- * has already passed. It was originally written to return 0 if
- * it was a timed event, and a repeating event would skip to the
- * next repeat who's alarm advance was still pending. Since I need
- * to know when we are amidst the alarm advance I always want to
- * return the next alarm that hasn't happened regardless of the
- * alarm advance. Actually it still returns 0 if the "end on"
- * date has passed.
- *
- ***********************************************************************/
- ULong ApptGetAlarmTime(ApptDBRecordPtr apptRec, ULong currentTime,
- DateTimeType *actualdt)
- {
- ULong advance;
- ULong alarmTime;
- DateType repeatDate;
- DateTimeType curDateTime;
- DateTimeType apptDateTime;
-
- /* Non-repeating appointment? */
- if (! apptRec->repeat) {
- /* An alarm on an untimed event triggers at midnight. */
- if (TimeToInt(apptRec->when->startTime) == apptNoTime) {
- apptDateTime.minute = 0;
- apptDateTime.hour = 0;
- } else {
- apptDateTime.minute = apptRec->when->startTime.minutes;
- apptDateTime.hour = apptRec->when->startTime.hours;
- }
- apptDateTime.second = 0;
- apptDateTime.day = apptRec->when->date.day;
- apptDateTime.month = apptRec->when->date.month;
- apptDateTime.year = apptRec->when->date.year + firstYear;
-
- /*
- * Compute the time of the alarm by adjusting the date and time
- * of the appointment by the length of the advance notice.
- */
- advance = apptRec->alarm->advance;
- switch (apptRec->alarm->advanceUnit) {
- case aauMinutes:
- advance *= minutesInSeconds;
- break;
- case aauHours:
- advance *= hoursInSeconds;
- break;
- case aauDays:
- advance *= daysInSeconds;
- break;
- }
-
- alarmTime = TimDateTimeToSeconds(&apptDateTime) - advance;
- /* always return these even if the alarm advance has passed */
- *actualdt = apptDateTime;
- return (alarmTime);
- }
-
- /* Repeating appointment. */
- TimSecondsToDateTime(currentTime, &curDateTime);
- repeatDate.year = curDateTime.year - firstYear;
- repeatDate.month = curDateTime.month;
- repeatDate.day = curDateTime.day;
-
- while (ApptNextRepeat(apptRec, &repeatDate)) {
- /* An alarm on an untimed event triggers at midnight. */
- if (TimeToInt(apptRec->when->startTime) == apptNoTime) {
- apptDateTime.minute = 0;
- apptDateTime.hour = 0;
- } else {
- apptDateTime.minute = apptRec->when->startTime.minutes;
- apptDateTime.hour = apptRec->when->startTime.hours;
- }
- apptDateTime.second = 0;
- apptDateTime.day = repeatDate.day;
- apptDateTime.month = repeatDate.month;
- apptDateTime.year = repeatDate.year + firstYear;
-
- /*
- * Compute the time of the alarm by adjusting the date and time
- * of the appointment by the length of the advance notice.
- */
- advance = apptRec->alarm->advance;
- switch (apptRec->alarm->advanceUnit) {
- case aauMinutes:
- advance *= minutesInSeconds;
- break;
- case aauHours:
- advance *= hoursInSeconds;
- break;
- case aauDays:
- advance *= daysInSeconds;
- break;
- }
-
- alarmTime = TimDateTimeToSeconds(&apptDateTime) - advance;
- /*
- * Always return these even if the alarm advance has passed. It was
- * originally written to go to the next repeat if the alarm advance
- * has already passed. I don't want that because I have a preference
- * option that lets me show these events while they are between the
- * alarm advance and the event.
- */
- *actualdt = apptDateTime;
- return (alarmTime);
-
- }
-
- /* the event has an "end on" date that has passed */
- return (0);
- }
-
- /*
- * This makes a list of the silent alarms that went off today for untimed
- * events. There is also a preference that lets you choose to have the item
- * appear each day until the event.
- */
- UInt ApptGetUntimedAlarms(DmOpenRef dbP, DateType date, Word days,
- VoidHand apptLists [], UInt counts [])
- {
- ApptInfoPtr apptList;
- LineItemPtr LineItem;
- LineItemType tli;
- UInt i;
- UInt totalcount=0;
- UInt origcount;
- ULong shrunk;
- DateTimeType alarmdt, nextdt, actualdt;
- TimeType notime={0, 0};
- DateType nodate={0, 0}, actuald, nextd;
- ULong alarmTime;
- UInt recordNum;
- UInt numRecords;
- ULong next;
- VoidHand recordH;
- ApptDBRecordType apptRec;
- ApptPackedDBRecordPtr r;
- UInt startDate;
- UInt endDate;
- DateType tempDate;
- Boolean dbk3done;
- Word daysm1=days-1;
-
- /*
- * Get the seconds that represents 00:00 of the current day.
- */
- alarmdt.month = date.month;
- alarmdt.day = date.day;
- alarmdt.year = date.year+1904;
- alarmdt.hour = 0;
- alarmdt.minute = 0;
- alarmdt.second = 0;
- alarmTime = TimDateTimeToSeconds(&alarmdt);
-
- /*
- * Our span of "reasonable" appointments to check for are between today
- * and 14 days from now. No reasonable person would set an untimed alarm
- * in excess of that.
- */
- startDate = DateToInt(date);
- tempDate = date;
- DateAdjust(&tempDate, 13);
- endDate = DateToInt(tempDate);
-
- /*
- * Keep in mind that AddAppointmentToList() is expecting the list to
- * be locked already if it exists. So we must lock it down. Don't worry
- * about saving the pointer because it does a MemDeref(). In addition,
- * the list was most likely trimmed and needs to be padded to multiples
- * of 10 items. But that's taken care of in the function.
- */
- if (apptLists[daysm1]) {
- MemHandleLock(apptLists[daysm1]);
- /*
- * There are probably some packed LineItemType elements already
- * present since the handle exists. We want to skip over them and
- * add ApptTypeInfo structures, then sort, and then shrink the new
- * elements down to LineItemType as well.
- */
- shrunk = counts[daysm1]*sizeof(LineItemType);
- } else shrunk = 0;
-
- origcount = counts[daysm1]; /* for the sorting at the end */
-
- /* Search the database for appointments with alarms at the passed time. */
- numRecords = DmNumRecords(dbP);
-
- /*
- * We'll start searching for untimed alarms from today. This is the
- * loop for non-repeats.
- */
- ApptFindFirst(dbP, date, &recordNum);
-
- for (; recordNum < numRecords; recordNum++) {
- recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
- if (! recordH) break;
-
- r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
-
- if (r->flags.alarm) {
- ApptUnpack(r, &apptRec);
-
- if ((DateToInt(apptRec.when->date) < startDate) ||
- (DateToInt(apptRec.when->date) > endDate)) {
- MemHandleUnlock(recordH);
- break;
- }
-
- if (TimeToInt(apptRec.when->startTime) == apptNoTime) {
- next = ApptGetAlarmTime(&apptRec, alarmTime, &actualdt);
- TimSecondsToDateTime(next, &nextdt);
- nextd.month = nextdt.month;
- nextd.day = nextdt.day;
- nextd.year = nextdt.year-1904;
- actuald.month = actualdt.month;
- actuald.day = actualdt.day;
- actuald.year = actualdt.year-1904;
- /*
- * Check if it lands on the current day. But also we don't
- * want to show it if it has a 0 advance (day == day).
- *
- * But also we want to keep an item on the list if the user
- * selects the preference.
- */
- if (next &&
- (DateToInt(date) != DateToInt(actuald)) &&
- ((DateToInt(nextd) == DateToInt(date)) ||
- ((PrefsR->ToDoPrefs.showua == '1') &&
- (DateToInt(nextd) <= DateToInt(date))))) {
-
- /* Check if the datebk3 "done" flag is set */
- dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
- Datebk3Done(r);
- if (!dbk3done) {
- /*
- * Add a title for the untimed listings. This is drawn
- * in the table callback.
- */
- if ((totalcount == 0) &&
- (AddAppointmentToList(&apptLists[daysm1], shrunk,
- counts[daysm1]-origcount, origcount, notime,
- notime, nodate, 0, 3))) {
- /*
- * apptAlarmLine is the line number where the
- * Untimed Alarms start. It is linear from the
- * start of the list so we need to add up the
- * previous counts.
- */
- apptAlarmLine = 0;
- for (i=0; i < days; i++) apptAlarmLine += counts[i];
- counts[daysm1]++;
- totalcount++;
- }
- /* Add the record to the alarm list. */
- if (AddAppointmentToList(&apptLists[daysm1], shrunk,
- counts[daysm1]-origcount, origcount,
- apptRec.when->startTime,
- apptRec.when->endTime, actuald, recordNum, 0)) {
- counts[daysm1]++;
- totalcount++;
- }
- }
- }
- }
-
- }
- MemHandleUnlock(recordH);
- }
-
- /*
- * Now we do the repeat events which are at the beginning of the database.
- */
-
- for (recordNum=0; recordNum < numRecords; recordNum++) {
- recordH = DmQueryNextInCategory(dbP, &recordNum, dmAllCategories);
- if (! recordH) break;
-
- r = (ApptPackedDBRecordPtr) MemHandleLock(recordH);
-
- /* stop when there are no more repeat records */
- if (!r->flags.repeat) {
- MemHandleUnlock(recordH);
- break;
- }
-
- if (r->flags.alarm) {
- ApptUnpack(r, &apptRec);
-
- if (TimeToInt(apptRec.when->startTime) == apptNoTime) {
- next = ApptGetAlarmTime(&apptRec, alarmTime, &actualdt);
- TimSecondsToDateTime(next, &nextdt);
- nextd.month = nextdt.month;
- nextd.day = nextdt.day;
- nextd.year = nextdt.year-1904;
- actuald.month = actualdt.month;
- actuald.day = actualdt.day;
- actuald.year = actualdt.year-1904;
- if (next &&
- (DateToInt(actuald) >= startDate) &&
- (DateToInt(actuald) <= endDate) &&
- (DateToInt(date) != DateToInt(actuald)) &&
- ((DateToInt(nextd) == DateToInt(date)) ||
- ((PrefsR->ToDoPrefs.showua == '1') &&
- (DateToInt(nextd) <= DateToInt(date))))) {
- /* Check if the datebk3 "done" flag is set */
- dbk3done = (PrefsR->ApptPrefs.showdone == '0') &&
- Datebk3Done(r);
- if (!dbk3done) {
- /*
- * Add a title for the untimed listings. This is drawn
- * in the table callback.
- */
- if ((totalcount == 0) &&
- (AddAppointmentToList(&apptLists[daysm1], shrunk,
- counts[daysm1]-origcount, origcount, notime,
- notime, nodate, 0, 3))) {
- /*
- * apptAlarmLine is the line number where the
- * Untimed Alarms start. It is linear from the
- * start of the list so we need to add up the
- * previous counts.
- */
- apptAlarmLine = 0;
- for (i=0; i < days; i++) apptAlarmLine += counts[i];
- counts[daysm1]++;
- totalcount++;
- }
- /* Add the record to the alarm list. */
- if (AddAppointmentToList(&apptLists[daysm1], shrunk,
- counts[daysm1]-origcount, origcount,
- apptRec.when->startTime,
- apptRec.when->endTime, actuald, recordNum, 0)) {
- counts[daysm1]++;
- totalcount++;
- }
- }
- }
- }
- }
-
- MemHandleUnlock(recordH);
- }
-
- /*
- * This was locked inside AddAppointmentToList, and it is also buffered
- * to 10 positions and must be trimmed. Also we want to sort the end
- * of the list.
- */
- if (apptLists[daysm1]) {
- if (counts[daysm1] > origcount) {
- /*
- * Get the pointer again. Yeah I know this sucks having to
- * deref the handle again but I didn't want to have to carry
- * around a pointer.
- */
- MemHandleUnlock(apptLists[daysm1]);
- apptList = MemHandleLock(apptLists[daysm1]);
- apptList = (ApptInfoPtr) (((char *) apptList)+shrunk);
- if ((counts[daysm1]-origcount-1) >= 2) {
- SysInsertionSort(&apptList[1], counts[daysm1]-origcount-1,
- sizeof(ApptInfoType), (_comparF *) ApptListCompare, 0L);
- }
- /*
- * Shrink the array down to a more compact structure because we
- * don't need many of the structure elements anymore.
- */
- LineItem = (LineItemPtr) apptList;
- for (i=0; i < (counts[daysm1]-origcount); i++) {
- /*
- * Both LineItem & apptList point to the same spot so make
- * sure to use a temporary holding place when copying.
- */
- tli.recordNum = apptList[i].recordNum;
- tli.startTime = apptList[i].startTime;
- tli.date = apptList[i].date;
- LineItem[i] = tli;
- }
- MemHandleUnlock(apptLists[daysm1]);
- MemHandleResize(apptLists[daysm1],
- counts[daysm1]*sizeof(LineItemType));
- } else MemHandleUnlock(apptLists[daysm1]);
- }
-
- return totalcount;
- }
-